﻿using Occt;
using System;
using System.Collections.Generic;
using System.Linq;
using Tx.Collections;

namespace Tx.OCC
{
    public class AssemblyTree
    {
        public class Info
        {
            public string baseName { get; set; }
            public TDF_Label Label { get; set; }
            public TopoDS_Shape Shape { get; set; }
            public Quantity_ColorRGBA Color { get; set; }

            public string MaterialName { get; set; }
            public string MaterialDescription { get; set; }
            public double MaterialDensity { get; set; }
            public TopLoc_Location LocalLocation { get; set; }
            public List<Info> Nodes { get; private set; } = new List<Info>();

            public void GetMaterial()
            {
                if (Label != null && XCAFDoc_MaterialTool.GetMaterial(Label, out var aName, out var aDescription, out var aDensity, out var aDensName, out var aDensValType))
                {
                    MaterialName = aName.ToString();
                    MaterialDescription = aDescription.ToString();
                    MaterialDensity = aDensity;
                }
            }
        }
        public List<Info> Nodes { get; private set; } = new List<Info>();
        private Dictionary<string, int> nameNumMap = new Dictionary<string, int>();

        private string NewName(string name)
        {
            nameNumMap.FindOrAdd(name, 0);
            ++nameNumMap[name];
            if (nameNumMap[name] > 1)
                return name + " " + nameNumMap[name].ToString("000");
            return name;
        }

        private XCAFDoc_ColorTool aColorTool;
        private XCAFDoc_ShapeTool aShapeTool;
        private XCAFDoc_MaterialTool aMaterialTool;
        public AssemblyTree(TDF_Label mainLabel)
        {
            aShapeTool = XCAFDoc_DocumentTool.ShapeTool(mainLabel);
            aColorTool = XCAFDoc_DocumentTool.ColorTool(mainLabel);
            aMaterialTool = XCAFDoc_DocumentTool.MaterialTool(mainLabel);
            aShapeTool.GetFreeShapes(out var labels);

            foreach (var freeLabel in labels)
            {
                var info = loadShape(freeLabel, XCAFDoc_ShapeTool.GetShape(freeLabel));
                if (info != null)
                    Nodes.Add(info);
            }
        }

        public static AssemblyTree Load(string fileName)
        {
            var reader = new STEPCAFControl_Reader();
            reader.ColorMode = true;
            reader.NameMode = true;
            reader.LayerMode = true;
            if (reader.ReadFile(fileName) != IFSelect_ReturnStatus.IFSelect_RetDone)
                return null;

            // Set up the reader and the document
            XCAFApp_Application.Application.NewDocument("MDTV-XCAF", out TDocStd_Document document);
            // Transfer file to a document
            if (!reader.Transfer(document))
                return null;
            return new AssemblyTree(document.Main);
        }
        public bool getColor(TopoDS_Shape shape, Info info)
        {
            if (aColorTool.GetColor(shape, XCAFDoc_ColorType.XCAFDoc_ColorSurf, out Quantity_ColorRGBA aColor))
            {
                info.Color = aColor;
                return true;
            }
            if (info.Color == null && aColorTool.GetColor(shape, XCAFDoc_ColorType.XCAFDoc_ColorGen, out aColor))
            {
                info.Color = aColor;
                return true;
            }
            return false;
        }


        private Info loadShape(TDF_Label label, TopoDS_Shape shape)
        {
            var baseShape = shape.Located(new TopLoc_Location());

            Info info = new Info(){ Label = label, Shape = shape, baseName = NewName(label.LabelName()) };
            getColor(shape, info);
            info.LocalLocation = XCAFDoc_ShapeTool.GetLocation(label);
            info.GetMaterial();

            var baseLabel = aShapeTool.FindShape(baseShape);
            bool res = false;
            if (baseLabel.IsNull || !XCAFDoc_ShapeTool.IsAssembly(baseLabel))
                res = createObject(baseLabel, baseShape, info);
            else
                res = createAssembly(baseLabel, baseShape, info);
            return info;
        }

        private bool createAssembly(TDF_Label label, TopoDS_Shape shape, Info info)
        {
            for (var it = new TopoDS_Iterator(shape, false, false); it.More; it.Next())
            {
                TopoDS_Shape childShape = it.Value;
                if (childShape == null || childShape.IsNull)
                    continue;
                if (!aShapeTool.Search(childShape, out var childLabel, true, true, false))
                    childLabel = null;

                var item = loadShape(childLabel, childShape);
                if (item == null)
                    continue;
                bool vis = true;
                if (!childLabel.IsNull && XCAFDoc_ShapeTool.IsComponent(childLabel))
                    vis = aColorTool.IsVisible(childLabel);

                info.Nodes.Add(item);
            }

            return info.Nodes.Count > 0;
        }

        private bool createObject(TDF_Label label, TopoDS_Shape shape, Info info)
        {
            if (shape == null || !new TopExp_Explorer(shape, TopAbs_ShapeEnum.TopAbs_VERTEX).More)
            {
                return false;
            }


            int childSolidCount = shape.SubShapes<TopoDS_Solid>().Count();
            int childShellCount = shape.SubShapes<TopoDS_Shell>().Count();
            if ((childSolidCount > 1 || (childSolidCount > 0 && childShellCount > 1)))
            {
                return expandShape(label, shape, info);
            }
            else
            {
                Info info1 = new Info() { Label = label, Shape = shape, baseName = NewName(shape.ShapeType.ToString()) };
                getColor(shape, info1);
                info1.LocalLocation = label == null ? new TopLoc_Location() : XCAFDoc_ShapeTool.GetLocation(label);
                info.Nodes.Add(info1);
                return true;
                //                 feature = static_cast<Part::Feature*>(doc.addObject("Part::Feature", tshape.shapeName().c_str()));
                //                 feature.Shape.setValue(shape);
                // feature.Visibility.setValue(false);
            }
        }

        private bool expandShape(TDF_Label label, TopoDS_Shape shape, Info info)
        {
            if (shape == null || !new TopExp_Explorer(shape, TopAbs_ShapeEnum.TopAbs_VERTEX).More)
                return false;


            // When saved as compound, STEP file does not support instance sharing,
            // meaning that even if the source compound may contain child shapes of
            // shared instances, or multiple hierarchies, those information are lost
            // when saved to STEP, everything become flat and duplicated. So the code
            // below is not necessary.

            if (shape.ShapeType == TopAbs_ShapeEnum.TopAbs_COMPOUND)
            {
                for (var it = new TopoDS_Iterator(shape, false, false); it.More; it.Next())
                {
                    if (!aShapeTool.FindSubShape(label, it.Value, out var childLabel))
                        childLabel = null;


                    var child = expandShape(childLabel, it.Value, info);
                    if (child)
                    {
                        //myShapes.emplace(it.Value().Located(TopLoc_Location()),info);
                    }
                }

                return true;
            }

            return createObject(label, shape, info);
        }

    }
}
